chore(tooling): census the workspace test-tsconfig divergence matrix (objectui#8714) - #8720
Merged
Merged
Conversation
…(objectui#8714) Three per-package `tsconfig` divergences had been found — each by a developer tripping over it, none by any instrument — and the card asked the question none of them could answer: how many packages differ, on how many axes, with how many distinct values each. Adds the instrument and the reading: - `scripts/tsconfig-test-parity-census.mjs` (`pnpm census:tsconfig-test-parity`) resolves every `tsconfig.test.json` through TypeScript's own config resolver, so `extends` chains flatten the way `tsc` flattens them, and groups the 38 projects by their effective value on every option. Report-only: it never fails on a divergence, because several of them are deliberate and load-bearing. The one thing it refuses is a silent zero — an empty population exits non-zero, since a census blind to its own corpus is indistinguishable from a repo whose test programs agree. - `docs/audits/2026-09-test-tsconfig-parity-census.md` records the matrix: 42 test type-programs, 38 with a dedicated project, 2,477 program files, 23 divergent axes, and 4 behavioural axes across the 35-package sibling cohort (`types` 6 values, `lib` 4, `include` 3, `exclude` 2). It also records the two routes a diff of the config text cannot see. A bare `import '@testing-library/jest-dom'` augments the matchers program-wide, so 83 files across four packages type-check only because a DIFFERENT file in the same package imports it; and one `/// <reference types="node" />` gives `sdui-parser` the Node globals its ten `types`-unset siblings do not have. No `tsconfig` is edited and no gate is added. The audit proposes what a gate should assert and what it would have caught; ruling on that is a separate card. The one conditional fix the card authorised — naming jest-dom in `plugin-timeline` — is declined with evidence: 21 of 35 siblings do not name it, `permissions` documents its omission on purpose, and `fields` uses matchers in 86 files with `types` unset entirely, so there is no uniform practice to have deviated from. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
os-justin
marked this pull request as ready for review
September 9, 2026 01:12
os-justin
enabled auto-merge
September 9, 2026 01:12
This was referenced Sep 9, 2026
`scripts/tsconfig-test-parity-census.mjs` hand-typed its entry guard as `process.argv[1] && fileURLToPath(import.meta.url) === process.argv[1]`, which `check:entry-guard` rejects. Node realpaths `import.meta.url` but leaves `argv[1]` as the caller typed it, so a script reached through a symlink compares two different paths, answers false, and does nothing -- exit 0, no output, indistinguishable from a pass to any wrapper holding only the exit status. Measured on this file, before and after. Old spelling: run directly it prints 25047 bytes of census; reached through a symlink it printed 0 bytes and exited 0. Through `isEntrypoint` both routes print the same 25047 bytes, and importing the module for its exports still prints nothing. The gate reported line 261 twice because it counts token occurrences rather than guard sites -- that one line spelled `process.argv[1]` twice, once as the truthiness leg and once as the comparand. Refs: objectui#8714 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Part of #8714 — the census the card asked for. The instance it names is not fixed here, deliberately; see The one-line fix below.
The number that decides the card
tsconfig.test.json(this census)tsconfig.jsonapps/console,data-objectstack,cli,test-support)types(6 values),lib(4),include(3),exclude(2)So it is not "two packages differ on one key" — it is 35 siblings, four behavioural axes, none with fewer than two values and one with six.
What lands
scripts/tsconfig-test-parity-census.mjs(pnpm census:tsconfig-test-parity) — resolves everytsconfig.test.jsonthrough TypeScript's own config resolver, soextendschains flatten exactly the waytscflattens them, and groups the projects by effective value per option. Report-only; it never fails on a divergence. The one thing it refuses is a silent zero: an empty population exits non-zero, because a census blind to its own corpus is indistinguishable from a repo whose test programs agree.docs/audits/2026-09-test-tsconfig-parity-census.md— the matrix, LATENT/LIVE per divergence, a deliberate-vs-accidental verdict per row with its reason, and a proposed gate.scripts/__tests__/tsconfig-test-parity-census.test.ts— 8 pins, including the emptiness leg.⛔ No
tsconfigis edited and no gate is added. Several divergences are deliberate and load-bearing; flattening them would be a repo-wide behaviour change dressed as tidying. What a gate should assert is written down in the audit; implementing it is a separate card.Two axes that are not in any tsconfig
A diff of the config text cannot produce these, which is why three cards each found "the" divergence and none found the set:
import '@testing-library/jest-dom'augments the matchers program-wide.plugin-detail(40 files),plugin-dashboard(16),fields(15) andplugin-list(12) type their matchers only because a different file in the same package imports it. Deleting the last importing file fromplugin-detailturns 40 filesTS2339in one commit, and nothing names the dependency.sdui-parserandreact-runtimeboth leavetypesunset;processresolves in one and isTS2591in the other.--explainFilesnames the cause: a/// <reference types="node" />on line 1 of onesdui-parsertest file. It is the only one of the 38 whose Node types arrive that way.Already paid — twice, in workarounds
Every one of the 38 programs is green, so the divergences are latent for compilation. They are live as workaround cost, which is the expensive kind because CI cannot see it:
plugin-timeline/src/ObjectTimeline.expandFls-7429.test.tsx:147usestoBeTruthy()with a comment, from test(plugin-map,plugin-timeline): pin what these two contractEnvelope-6839 waits were standing on #8713, because the matcher would not type-check. Untouched here, as instructed.plugin-designerdocument index arithmetic written instead of.at(-1). One of them generalises: ".at() type-checks nowhere in this repo" — 6 of the 38 programs are on ES2022, where it does. That false belief is what the absence of a census costs.The one-line fix, and why it was not made
#8714 authorised naming jest-dom in
plugin-timelineonly if the census showed the omission accidental — "its siblings uniformly name jest-dom and nothing explains the exception." The condition fails: 21 of 35 cohort siblings do not name it,permissionsdocuments its omission on purpose, andfieldsuses matchers in 86 files withtypesunset entirely. There is no uniform practice to have deviated from.Measured for whoever rules on it (probe file, each leg restored after):
plugin-timelinetypesTS2339;processTS2591["@testing-library/jest-dom"]— the literal one lineprocessstillTS2591["node", "@testing-library/jest-dom"]— the shape 7 siblings useIf triage rules "fix the instance", the third row is the shape, not the first.
Verification
Precondition —
pnpm build, 43/43 tasks green, before anytsc -p. An unbuilt tree givesTS2307for every workspace import and makes all 38 type-programs look broken identically.Baseline — all 38 projects compiled individually:
exit=0 errors=0, 38/38.Control legs — a probe file in
plugin-timeline's program, both errors fired, so the zeros elsewhere are readings and not blindness:The second reproduces the card's claim exactly.
Deliberate-vs-accidental, measured not read — adding
src/**/*.d.tstoplugin-view'sinclude(the shape 6 siblings carry) turns that project RED with 7 errors: its ownsrc/global.d.tsredeclaresprocessand collides with@types/node. The exclusion is load-bearing and undocumented. Three of the eight rows in the audit needed atscrun to classify — you cannot read this off the config text.Ablation on the new pin — the emptiness guard removed from the committed script;
1 failed | 7 passed, and the failure is exactlyFAILS on an empty population instead of reporting a uniform repo(expected +0 to be 1). Restored, verified bygit diff HEADempty and by blob hash0d62b3b6matchingHEAD. A second ablation droppingcompositefrom the defaults set failed exactly the spelling-normalisation pin. An assertion never observed failing has not been tested; both were.Gates —
pnpm lint:root0 errors;pnpm type-check:scriptsexit 0, with--listFilesconfirming it really reads the new test file;pnpm exec vitest run scripts/128 files passed, 2 skipped;check-control-bytes,check-lint-coverage,check-type-check-coverage,check-changeset-presence,check-changeset-no-major,check-changeset-fixed,check-changeset-overwrite,check-doc-fence-languages,check-doc-links,check-doc-expression-carriage,check-shell-escape-residue,check-unreferenced-sources,check-bash32-floor,check-published-tsconfig-tooling-exclude— all exit 0.check-governed-queue-guard --teston the five changed paths:NOT GOVERNED.check:node-esm-load— NOT MEASURED, not red. It refused 19 of 37 entries on provenance: turbo shares one cache across every worktree of this checkout, so sibling agents' build artifacts replayed into this tree. It grades publisheddistentries and this diff touches no package source. (Turbo caches are content-addressed by input hash, so a replay implies byte-identical inputs — but the gate's own remedy is--force-build, and CI builds fresh.)Changeset — empty frontmatter, the declared no-release form. The gate's own verdict: "No source or published contract of a released package changed in this range, so no changeset is owed."
What is left for triage
plugin-timeline's omission should be fixed at all, and if so in which shape.liblevels per package, so.at(-1)compiles in 75 test files and isTS2550in others #8691, the falseplugin-designercomment, and 83 leaning files).plugin-designercomment that generalises falsely, and the 3includepatterns matching zero files.None of those is done here. #8691 and #8710 are not addressed by this PR either — #8710's axis is between Vite's alias table and the
exportsmap, not between two tsconfigs, so this instrument structurally cannot see it.🤖 Generated with Claude Code
https://claude.ai/code/session_01YBWFb5YgMU5dw8p2VKj16S
Generated by Claude Code